home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Graphics⁄Sound / ROT-PSIG .c < prev   
Encoding:
C/C++ Source or Header  |  1986-05-23  |  18.2 KB  |  422 lines  |  [TEXT/EDIT]

  1.                       /* -------------------------------------------- */
  2.                       /*                3D Demonstration  (DRAFT)                      */
  3.                        /*                as told in Megamax C V2.1                         */
  4.                         /*                                     by                                             */
  5.                       /*      M H Miller                       ... 30 March 1986       */
  6.                       /*      wire frame                           10 April 1986        */
  7.                       /*      rotations                               15 April 1986    */
  8.                       /*      visibility                                    1 May 1986    */
  9.                       /*     touchup                                    3 May 1986        */
  10.                       /*     add 1st order lighting              8 May 1986       */
  11.                         
  12.     /* This is an 'application' which eventually will take its proper place among a set of exercises 
  13.       * whose purpose is enhancing understanding of C programming (Megamax C is the particular
  14.       * vehicle used),  and in facilitating oozing through Inside Macintosh.  In most of these
  15.       * exercises the methodology is to explore a  number of Inside Macintosh functions and 
  16.       * procedures, observe their visual effects (if any), study their syntax, and in general 
  17.       * evaluate their use and usfulness in the Macintosh interface.   Modifications, enhancements,
  18.       * non_commercial uses of this material, and tactful comments are invited, indeed encouraged.
  19.       *
  20.       * In studying the exercise(s) keep in mind ( for my sake at least) that the principal intention 
  21.       * is not to optimize the operation(s) performed but rather to examine Macintosh operations 
  22.       * and C programming.  With that view what you don't care for should be considered an
  23.       * objective illustration of what not to do.
  24.       */
  25.                       
  26.             /* --------------------------------------------------------  
  27.               This exercise involves computation of the isometric projection of a solid and rotation
  28.  of that solid about an axis.  The character of the solid is constrained so that particular 
  29.  circumstances requiring extended computation and manipulation are avoided.  Specifically the
  30.  solid must have plane faces which are closed polygons.  The solid must be 'convex', i.e., the 
  31.  angle between outward normals to two contiguous faces must be greater than 90 degrees; this
  32.  eliminates solids with 'holes', indentations, or 'pimples'.   For a convex solid a face either is
  33.  visible in its entirety or it isn't; there is no 'shadowing' of one face by others.   Rotations of
  34.  the solid are about a single (coordinate) axis at a time for illustrative simplicity, although
  35.  generalizing is not all that difficult or involved.   For each rotated position an equiangular
  36.  isometric projection is computed.  In addition the orientation of each face of a projection is 
  37.  computed to determine if the outward normal of a projected face points into or out of the
  38.  screen; a face is visible looking into the screen if its outward normal points out of the screen. 
  39.  A dynamic presentation of the rotating solid is drawn, optionally either as a wire frame or as
  40.  a solid.  Additional variations are planned for addition now and then.
  41.  
  42.  NOTE 1: The nature of the data structures used is of some importance.  However no great emphasis
  43.  was placed on optimizing these in this illustration.  Consider this illustration to be an illustration
  44.  and not a 'how to' prescription.  Nevertheless the program is written to allow easy redefinition of 
  45.  the object rotated, within the simplifying constraints noted.
  46.  
  47.  NOTE 2: This is a 'brute force' version to provide a reference against which to evaluate the
  48.  efficacy of measures to reduce flickering and image discontinuity.  Some modifications
  49.  to be added sooner or later are bitmap switching, assembly language interventions, synchronizing
  50.  to the blanking interrupts, and cleaning the screen as a VBL task.
  51.                  -------------------------------------------------------- */    
  52.  
  53. #include <stdio.h>        
  54. #include <event.h>        
  55. #include <qdvars.h>
  56. #include <control.h>
  57. #include <os.h>
  58. #include <fmath.h>
  59.  
  60. typedef  int void;                        /* Thoroughly Modern Miller */
  61.  
  62. #define X 0                                /* Axis references */
  63. #define Y 1
  64. #define Z 2 
  65.  
  66. /* For simplicity the object to be drawn is predefined.  The specification is for the 'base'
  67.      position from which all rotated positions are computed.  Within the constraints on the 
  68.      type of object allowed a new object can be redefined by redefining the parameters as
  69.      described or readily inferred.  The following three parameters are used to set 
  70.      computation loop limits; redefine to suit.
  71.   */
  72.     #define  NMBR_FACES 9
  73.     #define  NMBR_VERTICES 9
  74.     #define  NMBR_SETS 20            /* rotation angle step == 360/NMBR_SETS*/
  75.  
  76.  
  77.      /* Object vertex coordinates (relative to vertex 0).  This example is for a regular
  78.          trapazoidal parallelpiped.  This part of the specification only partially defines the
  79.          solid; still to come is specification of the face edges, i.e., the connections between
  80.          vertices which  define the individual faces.  Dimensions are referenced to LX, LY,
  81.          and LZ.  Note that rotations will be about coordinate axes for simplicity.  However that 
  82.          does not constrain the initial orientation of the object; it can be 'tilted' as desired.
  83.       */
  84.      #define LX 80
  85.     #define LY 40
  86.     #define LZ 60
  87.     
  88. /*    #####################################
  89.                                         'Plain Jane' initial orientation (not used)
  90.  
  91.     int vertex[ NMBR_VERTICES ][3] = {      { 0,0,0 },
  92.                                                                    { 0, 0,LZ },
  93.                                                                   { LX, 0,LZ },         
  94.                                                                  { LX, 0, 0 },        
  95.                                                                  {3*LX/4,LY, LZ/4},        
  96.                                                                  { 3*LX/4,LY,3*LZ/4 },         
  97.                                                                   {LX/4,LY,3*LZ/4},         
  98.                                                                   {LX/4,LY,LZ/4}, 
  99.                                                                  {LX/2,-LY/2,LZ/2}
  100.                                                                                                             };
  101.                                                                                                             
  102.     #####################################        */
  103.     
  104.     
  105.     /* 'Centerfold' (more exposed) initial position; scale not the same as for Plain Jane.  This
  106.          is the specification used for the program.
  107.          */
  108.     
  109.     int vertex[ NMBR_VERTICES ][3] = {          { 0,0,LZ/2 },
  110.                                                                      { LX/2, 0,LZ },         
  111.                                                                      { LX, 0, LZ/2 },
  112.                                                                      { LX/2,0,0 },
  113.                                                                      {LX/2,LY, LZ/4},        
  114.                                                                      { 3*LX/4,LY,LZ/2 },         
  115.                                                                      {LX/2,LY,3*LZ/4},         
  116.                                                                      {LX/4,LY,LZ/2},
  117.                                                                      {LX/2,-LY/2,LZ/2}
  118.                                                                                                             };                                                                                                        
  119.                                                               
  120.  /* A face is defined its edges; in turn these are defined by a set of vertices.  The vertex list
  121.        is determined as follows:  
  122.                a) Imagine the outward normal to the surface to be drawn.
  123.                b) Imagine your right hand curled around the normal, thumb pointing outward from the
  124.                    face along the normal.
  125.               c) Follow the curl of your fingers in listing the vertices forming the face edges.  Note 
  126.                   that the last vertex listed is the same as the first one; any of the face vertices may
  127.                be 'first' in the list.  
  128.      The number of vertices need not be the same for all faces.  Note again that faces are required 
  129.      to be planar polygons.  Violation of this constraint generally shows up as overlapping faces as
  130.      the object rotates.
  131.      The following 'face' specification is for the trapazoidal pyramid.  Per standard C rules any
  132.      array elements not specified will be set to 0, although this is not important since they are
  133.      not used at all.
  134.    */
  135.  int  face[NMBR_FACES][5] = {          {0,1,6,7,0},
  136.                                                       {1,2,5,6,1},
  137.                                                      {2,3,4,5,2},
  138.                                                       {7,4,3,0,7},
  139.                                                      {7,6,5,4,7},
  140.                                                      {0,3,8,0},
  141.                                                      {8,1,0,8},
  142.                                                      {8,2,1,8},
  143.                                                      {8,3,2,8}
  144.                                                      
  145.                                                                               };
  146.                                                                              
  147. /* Define the coordinates xr,yr of the center of rotation/origin (this will be taken to be 
  148.      somewhere in the plane of the screen), and the offset of vertex 0 from the center of rotation
  149.      (dxo, dyo, dzo).     Thus the absolute location of, say the X-coordinate of vertex 4, is 
  150.      xr+dxo+vertex[4][X].   See below for the computation of projection coordinates.
  151.   */
  152.   int  xr = 200, yr = 160, zr = 0, dxo = 50, dyo = 30, dzo = -50;     
  153.  
  154.  /* For each face a polygon will be computed (later) for the projection of that face */
  155.  polyhandle  face_poly[NMBR_SETS][NMBR_FACES];
  156.  
  157.  /* For each face, and in each set the direction of the normal to a projected face is computed 
  158.       to determine if that face is visible or not.  In addition the illumination ( yes or no) of a face
  159.       from a source to the left and up is determined.
  160.    */
  161. short visible[NMBR_SETS][ NMBR_FACES];    
  162. short light[NMBR_SETS][ NMBR_FACES];    
  163.  
  164. short drawflag;        /* Switch between drawing types.  SOLID illustrates the use of the 'visibility'
  165.                                   computation, i.e., only 'visible' faces are drawn.  SOLID_LIGHT adds 
  166.                                   first-order illumination.
  167.                               */
  168. #define SOLID_LIGHT 0
  169. #define SOLID  1
  170. #define WIRE_FRAME 2
  171.  
  172. rect option_rect, display_rect;            /* Housekeeping */
  173.  
  174. main()                  
  175. {    eventrecord the_event;
  176.     point mousepoint;
  177.      grafptr the_port;
  178.     int i,j;
  179.     register int  this_set,next_set;
  180.     rect temp_rect;
  181.     
  182.     initgraf(&theport);      
  183.     initfonts();        
  184.     initwindows();        
  185.     initcursor();
  186.     
  187.     /* Use the window manager port (desktop) directly.  Open up the clipregion to include the
  188.          menu bar and clear the screen.  Define a housekeeping rectangle for erasing the screen.
  189.       */
  190.     getwmgrport(&the_port);
  191.     setport(the_port);
  192.     cliprect(&(the_port->portrect));
  193.     eraserect(&(the_port->portrect));
  194.     setorigin(0,0);
  195.     setrect(&display_rect,0,21,512,342);
  196.     
  197.     /* Startup with SOLID_LIGHT, X-axis rotation */
  198.     compute_rotation_data(X);
  199.        drawflag = SOLID_LIGHT;
  200.     
  201.  
  202.     for(;;)
  203.     {           getnextevent(everyevent,&the_event);
  204.                if ( the_event.what == mousedown )
  205.                {    getmouse(&mousepoint);
  206.                 
  207.                     if (ptinrect(&mousepoint,&option_rect) ) 
  208.                     {    switch (i = mousepoint.a.h/15)
  209.                         {    case 0:                             /* Q for QUIT */
  210.                                 exit(0);   
  211.                                 break;
  212.                                  
  213.                             case 1: case 2:  case 3:   /* Rotate X, Y, Z respectively */
  214.                                 compute_rotation_data(i-1);
  215.                                 break;
  216.                             
  217.                             case 4:                                /* SOLID, SOLID_LIGHT, WIRE_FRAME */
  218.                                 if (++drawflag == 3)    drawflag = 0;
  219.                                    break;    
  220.                         }
  221.                     } else while(button())    ;       /* Freeze frame */
  222.                 }
  223.                 
  224.                  rotate_object();                    /* Take a step */
  225.     }
  226. return;
  227. }
  228.  
  229. void compute_rotation_data(axis)
  230. /* The isometric projection is an equiangular proection on the plane (screen).  The y-axis
  231.        coordinate of a point is drawn vertically (+ down) from the origin.  The x-axis position
  232.        is along a line drawn 30 degrees down from horizontal, while the z-axis is drawn along a 
  233.        line elevated 30 degrees above the horizontal.   The net result is that the screen position
  234.        relative to the origin of the point x, y, z is (in screen position coordinates)
  235.                             (x,y) = (x+z)*cos(30), y+(x-z)*sin(30)
  236.        This procedure computes the rotated position of the vertices, computes whether a 
  237.        projected face is 'visible' or not, and defines polygons for each face in each projection.
  238.        The data produced is 'face_poly[this_set][i]' which  is the polygon for drawing face#i after
  239.        a rotation of 360*this_set/NMBR_SETS.  No indication provided for axis of rotation.  In addition
  240.        'visible[this_set][i]' =1/0 indicates that face is visible/invisible seen looking into the screen.
  241.      
  242.      Note:  The only global parameters provided by this illustrative computation are the polygon 
  243.                 handles and the 'visibility' and 'light' arrays.
  244.     */
  245.  
  246.  int axis;
  247.  {    register int this_set,i,j;                        /* Rotation angle is 360*this_set/NMBR_SETS*/
  248.      int  basev[NMBR_VERTICES][3];            /* Vertex coordinates offset from center of rotation */
  249.     int v[NMBR_VERTICES][3];                    /* Vertex rotated coordinates  */
  250.     int p_vx[NMBR_VERTICES], p_vy[NMBR_VERTICES];  /* Projection coordinates */
  251.     int ax,ay,az,bx,by,bz;                /* Vector components for computing direction cosines */
  252.     
  253.     /* Miscellaneous utility variables*/
  254.     rect count_rect;                            
  255.      int v0,v1,v2;
  256.     float sin_table, cos_table; 
  257.     #define D_THETA  6.283185/NMBR_SETS
  258.      
  259.      /* Cf Psych 100 notes */
  260.      setrect(&count_rect,0,21,512,342);    
  261.      eraserect(&count_rect);
  262.      textsize(18); textface(bold); textfont(geneva); pennormal();
  263.      moveto(200,150);    drawstring("HOLD ON");
  264.      moveto(50,170);         drawstring("I'M COMPUTING WHAT I NEED TO KNOW");
  265.     
  266.     /* Compute the rotated position of the vertices.  Note that the computation references
  267.          the base vertex definitions for each computation rather than accumlating incremental
  268.          rotations.  For real time rotation computations the latter would be appropriate, provided
  269.          regular reinitialization, direct or indirect, avoided error accumulation.
  270.       */
  271.     
  272.     /* Locate vertices relative to center of rotation */
  273.     for(i=0;i<NMBR_VERTICES;++i)        
  274.     {    basev[i][X] = vertex[i][X]  + dxo;    
  275.         basev[i][Y] = vertex[i][Y]  + dyo;   
  276.         basev[i][Z] = vertex[i][Z]   + dzo;
  277.     }
  278.      
  279.     /* Angular increment is 360/NMBR_SETS */
  280.     for (this_set=0;this_set<NMBR_SETS;++this_set)        
  281.     {       /* Convenience variables */
  282.             sin_table  =  sin( D_THETA*this_set);   
  283.             cos_table = cos(D_THETA*this_set);    
  284.              
  285.            /* Rotate the vertices.  Note that the coordinates are relative to the center of rotation.*/
  286.              for ( i=0;i<NMBR_VERTICES;++i)        
  287.             {        switch (axis)        
  288.                     {        case Z:         
  289.                                     v[i][X] = basev[i][X]*cos_table - basev[i][Y]*sin_table;
  290.                                     v[i][Y] = basev[i][X]*sin_table + basev[i][Y]*cos_table;
  291.                                     v[i][Z] = basev[i][Z];
  292.                                     break;
  293.                             case Y:         
  294.                                     v[i][X] = basev[i][X]*cos_table + basev[i][Z]*sin_table;
  295.                                     v[i][Y] = basev[i][Y];
  296.                                     v[i][Z] = -basev[i][X]*sin_table + basev[i][Z]*cos_table;
  297.                                     break;
  298.                             case X:
  299.                                     v[i][X] = basev[i][X];
  300.                                     v[i][Y] = basev[i][Y]*sin_table +  basev[i][Z]*cos_table;
  301.                                     v[i][Z] = -basev[i][Y]*cos_table +  basev[i][Z]*sin_table;
  302.                                     break;
  303.                     }
  304.             }
  305.             
  306.             /* A source of light is assumed far away and to the right.  A face is illuminated if 'sees'
  307.                   the light, i.e., if its outward normal is directed to the right.  In addition of course
  308.                   the face will have to be visible in the projection, as computed later.  This illumination
  309.                   algorithm does not provided graded lighting as would occur in reality.  A face either
  310.                   is or is not illuminated. 
  311.                   Note: See comments below for 'visible' computation for additional pertinent remarks
  312.                   not included here. 
  313.                 */
  314.             for (    i=0;i<NMBR_FACES;++i)
  315.             {    v0= face[i][0];            v1= face[i][1];         v2= face[i][2];        
  316.                 ay = v[v2][Y] -  v[v1][Y];    by = v[v0][Y] -  v[v1][Y];
  317.                 az =  v[v2][Z] -  v[v1][Z];    bz =  v[v0][Z] -  v[v1][Z];
  318.                 
  319.                 light[this_set][i]=   (ay*bz- az*by)>0   ?0 :1;        /* An illuminated face has a 0 flag. */
  320.             }
  321.             
  322.             /* Compute the isometric positions for each vertex in the current set.  The projection is
  323.               * computed relative to the center of rotation as origin and then reset relative to the
  324.               * screen origin.
  325.               */
  326.             for(i=0;i<NMBR_VERTICES;++i)
  327.             {    p_vx[i] = xr + .86603*(v[i][X] + v[i][Z]);
  328.                 p_vy[i] = yr +  v[i][Y] + (v[i][X] - v[i][Z])/2;
  329.             }        
  330.              
  331.             /* A face of the projection is 'visible' if it can be seen looking into the screen.  To
  332.               * determine visibility use the fact that faces are defined by the array face[][4] 
  333.               * which provides the clockwise sequence of vertices defining the face.  The
  334.               * procedure is:
  335.               *     a) Select three consecutive vertices #0,#1,#2 from the face definition vector
  336.               *        to define two projected face edges.  The first three are used.  Note that the
  337.               *        face definition vector lists vertices circulating CCW about outward directed normal.
  338.               *    b) Define edge#1 as fron vertex #1 to vertex #2; call this edge A.
  339.               *    c) Define edge#2 as fron vertex #1 to vertex #0; call this edge B.
  340.               *    d) The normal is A x B= Ax*By - Ay*Bx; if this is negative (out of the screen) the
  341.               *        face is visible.
  342.               *
  343.               * Note: Notational guide:
  344.               * The first three vertices of face #this_set are face[this_set][0], face[this_set][1], and 
  345.               * face[this_set][2].  Compute components as:
  346.               *            Ax = v[  face[this_set][2]  ][X] -  v[  face[this_set][1]  ][X] ;  
  347.               *            Bx = v[  face[this_set][0]  ][X] -  v[  face[this_set][1]  ][X] ;  
  348.               * The fact that the vertex coordinates are relative to the center of rotation washes out
  349.               * because of the difference is taken. 
  350.              */
  351.             
  352.             for (    i=0;i<NMBR_FACES;++i)
  353.             {    v0= face[i][0];            v1= face[i][1];         v2= face[i][2];        
  354.                 ax= p_vx[v2] -  p_vx[v1];    bx= p_vx[v0] -  p_vx[v1];
  355.                 ay= p_vy[v2] -  p_vy[v1];    by= p_vy[v0] -  p_vy[v1];
  356.                 
  357.                 /* Condition a flag  'visible[this_set][i]' to indicate visible faces. */ 
  358.                 visible[this_set][i]=   (ax*by- ay*bx)<0   ?1 :0;
  359.             }
  360.              
  361.              /* Compute the face polygons.  Note that restriction to convex solids means a face 
  362.                * either is visible or it is not; no partial visibility because of shadowing.
  363.                */
  364.              for( i=0; i<NMBR_FACES; ++i)
  365.              {        face_poly[this_set][i] = openpoly();
  366.                             moveto(  p_vx[  face[i][0]  ], p_vy[  face[i][0]  ]  );
  367.                             for(j=1;j<NMBR_VERTICES;++j)
  368.                             {    lineto(  p_vx[  face[i][j]  ], p_vy[  face[i][j]  ]  );
  369.                                 if( face[i][j] == face[i][0]  )        break;
  370.                             }
  371.                     closepoly();
  372.             }
  373.              
  374.             /* Display computational progress as a countdown */
  375.             setrect(&count_rect,200,175,250,205);
  376.             eraserect(&count_rect);
  377.             moveto(210,200);
  378.             i=(NMBR_SETS-this_set); 
  379.             if (i>99)     drawchar(48+i/100);   else   drawchar(' '); 
  380.             if (i>99)  { i%=100;     drawchar(48+i/10);  i%=10 ;  }     
  381.                     else{   if (i>9)      {drawchar(48+i/10);   i%=10;  } else  drawchar(' '); }    
  382.              drawchar(48+i);
  383.     }
  384.     
  385.     /* Draw 'option bar' */
  386.     textsize(12);    
  387.     setrect(&option_rect,0,0,76,20);        framerect(&option_rect);
  388.     moveto(5,16);        drawchar('Q');        moveto(0,15);        line(0,15);         
  389.     moveto(20,16);    drawchar('X');        moveto(0,30);        line(0,15);
  390.     moveto(35,16);    drawchar('Y');        moveto(0,45);        line(0,15);
  391.     moveto(50,16);    drawchar('Z');         moveto(0,60);        line(0,15);
  392.     moveto(65,16);    drawchar('F');
  393.     
  394. return;
  395. }
  396.     
  397. void rotate_object()
  398. {    register int i;    
  399.     static int set=0;
  400.     register polyhandle *poly;
  401.     
  402.     poly = &face_poly[set][0];
  403.     
  404.     eraserect(&display_rect);
  405.     if ( drawflag == SOLID_LIGHT)  
  406.     {        for(i=0;i<NMBR_FACES;++i)    
  407.             {    if( visible[set][i] ==1) 
  408.                 {    if( light[set][i] ==1)       fillpoly( *(poly+i),<gray);
  409.                      framepoly( *(poly+i)); 
  410.                 }     
  411.             }
  412.     }
  413.     else if ( drawflag == SOLID)  
  414.             {    for(i=0;i<NMBR_FACES;++i)    
  415.                                     if ( visible[set][i] ==1)   framepoly(*(poly+i)); 
  416.              }
  417.             else     for(i=0;i<NMBR_FACES;++i)        framepoly(*(poly+i)); 
  418.          
  419.     if( ++set == NMBR_SETS )         set=0;     /* Update the rotation angle */
  420.                          
  421. return;